// 版权2019 etcd作者
// 
// 根据Apache许可证2.0版（以下简称“许可证”）获得许可；
// 除非遵守许可证，否则不得使用此文件。
// 您可以通过
// 
// www.apache.org/licenses/LICENSE-2.0 
// 
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，
// 无任何明示或暗示的保证或条件。
// 请参阅许可证，了解管理许可的特定语言和
// 许可证下的限制。

package confchange

import (
	pb "go.etcd.io/etcd/raft/v3/raftpb"
	"go.etcd.io/etcd/raft/v3/tracker"
)

// toConfChangeSingle将配置状态转换为1）一个操作片段，创建
// 首先是将成为传出配置的配置，然后是传入配置，以及
// b）另一个片段，当应用于由1）生成的配置时，表示
// ConfState。
func toConfChangeSingle(cs pb.ConfState) (out []pb.ConfChangeSingle, in []pb.ConfChangeSingle) {
	// 遵循此代码的示例：
	// 投票者=（1 2 3）学习者=（5）外向=（1 2 4 6）学习者下一步=（4）
	// 
	// 这意味着在进入联合配置之前，配置
	// 有投票者（1 2 4 6），可能还有一些已经离开的学习者。
	// 新的一组选民是（1 2 3），即（1 2）被保留，和（4 6）
	// 不再是选民；然而，4在离开
	// /联合州后将成为一名学习者。
	// 在进入联合配置之前，我们无法判断5是否是一名学习者，但这并不重要（我们会假装不是）。
	// 
	// 下面的代码将构造
	// outgoing=add 1；增加2个；增加4个；增加6个
	// 传入=删除1个；移除2个；移除4个；删除6个
	// 添加1个；增加2个；增加3个；
	// 增加学习者5；
	// 添加学习者4；
	// 
	// 因此，当以空配置开始时，在应用“传出”后，我们有
	// 
	// quorum=（1 2 4 6）
	// 
	// 我们通过“传入”
	// 
	// quorum=（1 2 3）和（1 2 4 6）学习者=（5）学习者_next=（4）根据需要选择
	// 
	// 。

	for _, id := range cs.VotersOutgoing {
		// 如果有即将离任的选民，首先将他们逐一添加，以便
		// （非联合）配置包含所有选民。
		out = append(out, pb.ConfChangeSingle{
			Type:   pb.ConfChangeAddNode,
			NodeID: id,
		})

	}

	// 我们已经完成了传出片的构造，现在进入传入片
	// （它将应用于传出片创建的配置之上）。

	// 首先，我们将清除所有即将离任的选民。
	for _, id := range cs.VotersOutgoing {
		in = append(in, pb.ConfChangeSingle{
			Type:   pb.ConfChangeRemoveNode,
			NodeID: id,
		})
	}
	// 然后我们将添加新加入的选民和学习者。
	for _, id := range cs.Voters {
		in = append(in, pb.ConfChangeSingle{
			Type:   pb.ConfChangeAddNode,
			NodeID: id,
		})
	}
	for _, id := range cs.Learners {
		in = append(in, pb.ConfChangeSingle{
			Type:   pb.ConfChangeAddLearnerNode,
			NodeID: id,
		})
	}
	// 与LearnerNext相同；这些是我们希望成为学习者的节点，但
	// 目前是即将离职的配置中的投票者。
	for _, id := range cs.LearnersNext {
		in = append(in, pb.ConfChangeSingle{
			Type:   pb.ConfChangeAddLearnerNode,
			NodeID: id,
		})
	}
	return out, in
}

func chain(chg Changer, ops ...func(Changer) (tracker.Config, tracker.ProgressMap, error)) (tracker.Config, tracker.ProgressMap, error) {
	for _, op := range ops {
		cfg, prs, err := op(chg)
		if err != nil {
			return tracker.Config{}, nil, err
		}
		chg.Tracker.Config = cfg
		chg.Tracker.Progress = prs
	}
	return chg.Tracker.Config, chg.Tracker.Progress, nil
}

// Restore接受一个更改器（必须表示一个空配置），而
// 运行一系列更改，执行
// ConfState中描述的配置。
// 
// TODO（待定）这需要一个改变者，这太愚蠢了。通过确保更改者只需要一个进度图（而不是一个完整的跟踪器）就可以解决这个问题，此时可以直接使用LastIndex和MaxInflight，并单独编制结果。
func Restore(chg Changer, cs pb.ConfState) (tracker.Config, tracker.ProgressMap, error) {
	outgoing, incoming := toConfChangeSingle(cs)

	var ops []func(Changer) (tracker.Config, tracker.ProgressMap, error)

	if len(outgoing) == 0 {
		// 没有传出配置，所以只需逐个应用传入的更改即可。
		for _, cc := range incoming {
			cc := cc // 循环本地副本
			ops = append(ops, func(chg Changer) (tracker.Config, tracker.ProgressMap, error) {
				return chg.Simple(cc)
			})
		}
	} else {
		// ConfState描述了一个联合配置。
		// 
		// 首先，逐个应用传出配置的所有更改，使
		// 临时成为传入的活动配置。例如，
		// 如果配置为（1 2 3）和（2 3 4），则将建立（2 3 4）和（）。
		for _, cc := range outgoing {
			cc := cc // 循环本地副本
			ops = append(ops, func(chg Changer) (tracker.Config, tracker.ProgressMap, error) {
				return chg.Simple(cc)
			})
		}
		// 现在进入联合状态，它将上述添加旋转到
		// 传出配置中，并在中添加传入配置。继续上面的
		// 示例，我们将得到（1 2 3）和（2 3 4），即传入操作
		// 将删除2,3,4，然后添加1,2,3，同时将
		// 转换为联合状态。
		ops = append(ops, func(chg Changer) (tracker.Config, tracker.ProgressMap, error) {
			return chg.EnterJoint(cs.AutoLeave, incoming...)
		})
	}

	return chain(chg, ops...)
}
